use std::path::Path;

use bc_base::{BlockId, block::BlockIndexNode};
use bc_db::DBError;

use crate::{DbHashMap, inner_storage::StorageBase};

#[derive(Debug)]
pub(crate) struct BlockIndexStorage {
    index_db: StorageBase<BlockId, BlockIndexNode>,
}

impl BlockIndexStorage {
    pub(crate) fn new(db_root_dir: &Path) -> Result<Self, DBError> {
        let path = crate::get_blocks_index_db_path(db_root_dir);
        tracing::info!(
            ">>>> The path of BlockIndexStorage DB: {}",
            path.to_str().unwrap()
        );
        Ok(Self {
            index_db: StorageBase::new(&path)?,
        })
    }

    // insert or update
    pub(crate) async fn put(&self, key: &BlockId, value: &BlockIndexNode) -> Result<(), DBError> {
        self.index_db.put(key, value).await
    }

    pub(crate) async fn get(&self, key: &BlockId) -> Result<Option<BlockIndexNode>, DBError> {
        self.index_db.get(key).await
    }

    pub(crate) async fn erase(&self, key: &BlockId) -> Result<(), DBError> {
        self.index_db.erase(key).await
    }

    pub(crate) async fn exists(&self, key: &BlockId) -> bool {
        self.index_db.exists(key).await
    }

    pub(crate) async fn flush_database(&self) -> Result<(), DBError> {
        self.index_db.flush_database().await
    }

    pub(crate) async fn get_all_data(&self) -> DbHashMap<BlockId, BlockIndexNode> {
        self.index_db.get_all_data().await
    }

    #[cfg(debug_assertions)]
    pub(crate) async fn get_item_number(&self) -> usize {
        self.index_db.get_item_number().await
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use bc_base::{
        BlockChainHash,
        block::{Block, BlockHeader, BlockInDb},
    };
    use scopeguard::defer;
    fn new_node(timestamp: u32) -> BlockIndexNode {
        let version = 1;
        let prev_block_hash = BlockChainHash::new([100; 32]);
        let merkle_root_hash = BlockChainHash::new([200; 32]);
        let nbits = 1;
        let nonce = 1;
        let h = BlockHeader::new(
            version,
            &prev_block_hash,
            merkle_root_hash,
            timestamp,
            nbits,
            nonce,
        );

        let blk = Block::new(h);
        let blk_in_db = BlockInDb::new(&blk);
        BlockIndexNode::new_by_block_in_db(&blk_in_db)
    }

    #[tokio::test]
    async fn test_block_index_storage() {
        let tmp_dir = bc_util::convert_to_temp_dir("test_open_db");
        let path = tmp_dir.path().to_path_buf();
        defer! {
            tmp_dir.close().unwrap();
        };

        let id1 = BlockId::new([1; 32]);
        let node1 = new_node(1);
        let node1_1 = new_node(11);

        let id2 = BlockId::new([2; 32]);
        let node2 = new_node(2);

        let block_index_storage = BlockIndexStorage::new(&path).unwrap();

        assert!(!(block_index_storage.exists(&id1).await));
        block_index_storage.put(&id1, &node1).await.unwrap(); // insert
        assert!(block_index_storage.exists(&id1).await); // exists
        assert_eq!(block_index_storage.get(&id1).await.unwrap(), Some(node1)); // read
        block_index_storage.put(&id1, &node1_1).await.unwrap(); // update

        assert!(!(block_index_storage.exists(&id2).await));
        block_index_storage.put(&id2, &node2).await.unwrap();
        assert_eq!(
            block_index_storage.get(&id2).await.unwrap(),
            Some(node2.clone())
        );

        block_index_storage.flush_database().await.unwrap();
        let all_data = block_index_storage.get_all_data().await;
        assert_eq!(all_data.len(), 2);
        let map = DbHashMap::from([(id1, node1_1.clone()), (id2, node2)]);
        assert_eq!(all_data, map);
        for (k, v) in all_data {
            println!("key: {}, value: {:?}", k, v);
        }

        // for (k, v) in map {
        //     println!("key: {}, value: {:?}", k, v);
        // }

        block_index_storage.erase(&id2).await.unwrap();
        assert_eq!(block_index_storage.get(&id2).await.unwrap(), None);
        assert!(!(block_index_storage.exists(&id2).await));

        let all_data = block_index_storage.get_all_data().await;
        assert_eq!(all_data.len(), 1);
        let map = DbHashMap::from([(id1, node1_1)]);
        assert_eq!(all_data, map);
    }
}

#[cfg(test)]
mod tests_for_learning {
    use super::*;

    #[test]
    fn test_map() {
        let mut map1 = DbHashMap::new();
        map1.insert(1, "a");
        map1.insert(2, "b");
        map1.insert(3, "c");
        assert_eq!(map1.len(), 3);

        let map2 = DbHashMap::from([(11, "A")]);
        map1 = map2;
        assert_eq!(map1.len(), 1);
    }
}
